/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
/**********************************************************************
   module: SNDSRC.C

   author: James R. Dose
   date:   March 26, 1994

   Low level routines to support the Disney Sound Source.

   (c) Copyright 1994 James R. Dose.  All Rights Reserved.
**********************************************************************/

#define STEREO      1
#define SIXTEEN_BIT 2

#define MONO_8BIT    0
#define STEREO_8BIT  ( STEREO )
#define MONO_16BIT   ( SIXTEEN_BIT )
#define STEREO_16BIT ( STEREO | SIXTEEN_BIT )

#include <stdlib.h>
#include <dos.h>
#include <conio.h>
#include "dpmi.h"
#include "task_man.h"
#include "sndcards.h"
#include "user.h"
#include "sndsrc.h"

#define TRUE  ( 1 == 1 )
#define FALSE ( !TRUE )

static int SS_Installed = FALSE;

static int SS_Port = SS_DefaultPort;
static int SS_OffCommand = 0xc;

static char   *SS_BufferStart;
static char   *SS_BufferEnd;
static char   *SS_CurrentBuffer;
static int     SS_BufferNum = 0;
static int     SS_NumBuffers = 0;
static int     SS_TotalBufferSize = 0;
static int     SS_TransferLength  = 0;
static int     SS_CurrentLength   = 0;

static char   *SS_SoundPtr;
volatile int   SS_SoundPlaying;

static task   *SS_Timer;

void ( *SS_CallBack )( void );

int SS_ErrorCode = SS_Ok;

#define SS_SetErrorCode( status ) \
   SS_ErrorCode   = ( status );

/*---------------------------------------------------------------------
   Function: SS_ErrorString

   Returns a pointer to the error message associated with an error
   number.  A -1 returns a pointer the current error.
---------------------------------------------------------------------*/

char *SS_ErrorString
   (
   int ErrorNumber
   )

   {
   char *ErrorString;

   switch( ErrorNumber )
      {
      case SS_Error :
         ErrorString = SS_ErrorString( SS_ErrorCode );
         break;

      case SS_Ok :
         ErrorString = "Sound Source ok.";
         break;

      case SS_NotFound :
         ErrorString = "Could not detect Sound Source.";
         break;

      case SS_NoSoundPlaying :
         ErrorString = "No sound playing in SndSrc.";
         break;

      case SS_DPMI_Error :
         ErrorString = "DPMI Error in SndSrc.";
         break;

      default :
         ErrorString = "Unknown Sound Source error code.";
         break;
      }

   return( ErrorString );
   }


/**********************************************************************

   Memory locked functions:

**********************************************************************/


#define SS_LockStart SS_ServiceInterrupt


/*---------------------------------------------------------------------
   Function: SS_ServiceInterrupt

   Handles interrupt generated by sound card at the end of a voice
   transfer.  Calls the user supplied callback function.
---------------------------------------------------------------------*/

static void SS_ServiceInterrupt
   (
   task *Task
   )

   {
   int port = SS_Port;
   int count;

   count = 0;
   while( ( inp( port + 1 ) & 0x40 ) == 0 )
      {
      outp( port, *SS_SoundPtr++ );
      outp( port + 2, SS_OffCommand );
      outp( port + 2, 4 );

      SS_CurrentLength--;
      if ( SS_CurrentLength == 0 )
         {
         // Keep track of current buffer
         SS_CurrentBuffer += SS_TransferLength;
         SS_BufferNum++;
         if ( SS_BufferNum >= SS_NumBuffers )
            {
            SS_BufferNum = 0;
            SS_CurrentBuffer = SS_BufferStart;
            }

         SS_CurrentLength = SS_TransferLength;
         SS_SoundPtr = SS_CurrentBuffer;

         // Call the caller's callback function
         if ( SS_CallBack != NULL )
            {
            SS_CallBack();
            }
         }

      count++;
      // Only do at most 14 samples per tick
      if ( count > 13 )
         {
         break;
         }
      }
   }


/*---------------------------------------------------------------------
   Function: SS_StopPlayback

   Ends the transfer of digitized sound to the Sound Source.
---------------------------------------------------------------------*/

void SS_StopPlayback
   (
   void
   )

   {
   if ( SS_SoundPlaying )
      {
      TS_Terminate( SS_Timer );

      outp( SS_Port, 0x80 );
      outp( SS_Port + 2, SS_OffCommand );
      outp( SS_Port + 2, 4 );

      SS_SoundPlaying = FALSE;

      SS_BufferStart = NULL;
      }
   }


/*---------------------------------------------------------------------
   Function: SS_GetCurrentPos

   Returns the offset within the current sound being played.
---------------------------------------------------------------------*/

int SS_GetCurrentPos
   (
   void
   )

   {
   int offset;

   if ( !SS_SoundPlaying )
      {
      SS_SetErrorCode( SS_NoSoundPlaying );
      return( SS_Warning );
      }

   offset = ( int )( ( ( unsigned long )SS_SoundPtr ) -
      ( ( unsigned long )SS_CurrentBuffer ) );

   return( offset );
   }


/*---------------------------------------------------------------------
   Function: SS_LockEnd

   Used for determining the length of the functions to lock in memory.
---------------------------------------------------------------------*/

static void SS_LockEnd
   (
   void
   )

   {
   }


/*---------------------------------------------------------------------
   Function: SS_BeginBufferedPlayback

   Begins multibuffered playback of digitized sound on the Sound Source.
---------------------------------------------------------------------*/

int SS_BeginBufferedPlayback
   (
   char *BufferStart,
   int   BufferSize,
   int   NumDivisions,
   void ( *CallBackFunc )( void )
   )

   {
   if ( SS_SoundPlaying )
      {
      SS_StopPlayback();
      }

   SS_SetCallBack( CallBackFunc );

   SS_BufferStart     = BufferStart;
   SS_CurrentBuffer   = BufferStart;
   SS_SoundPtr        = BufferStart;
   SS_TotalBufferSize = BufferSize;
   SS_BufferEnd       = BufferStart + BufferSize;
   SS_TransferLength  = BufferSize / NumDivisions;
   SS_CurrentLength   = SS_TransferLength;
   SS_BufferNum       = 0;
   SS_NumBuffers      = NumDivisions;

   SS_SoundPlaying = TRUE;

//   SS_Timer = TS_ScheduleTask( SS_ServiceInterrupt, 438, 1, NULL );
   SS_Timer = TS_ScheduleTask( SS_ServiceInterrupt, 510, 1, NULL );
   TS_Dispatch();

   return( SS_Ok );
   }


/*---------------------------------------------------------------------
   Function: SS_GetPlaybackRate

   Returns the rate at which the digitized sound will be played in
   hertz.
---------------------------------------------------------------------*/

int SS_GetPlaybackRate
   (
   void
   )

   {
   return( SS_SampleRate );
   }


/*---------------------------------------------------------------------
   Function: SS_SetMixMode

   Sets the sound card to play samples in mono or stereo.
---------------------------------------------------------------------*/

int SS_SetMixMode
   (
   int mode
   )

   {
   mode = MONO_8BIT;
   return( mode );
   }


/*---------------------------------------------------------------------
   Function: SS_SetPort

   Selects which port to use to write to the Sound Source.
---------------------------------------------------------------------*/

int SS_SetPort
   (
   int port
   )

   {
   if ( SS_Installed )
      {
      SS_Shutdown();
      }

   SS_Port = port;

   return( SS_Ok );
   }


/*---------------------------------------------------------------------
   Function: SS_SetCallBack

   Specifies the user function to call at the end of a sound transfer.
---------------------------------------------------------------------*/

void SS_SetCallBack
   (
   void ( *func )( void )
   )

   {
   SS_CallBack = func;
   }


/*---------------------------------------------------------------------
   Function: SS_TestTimer

   Used as a delay in SS_TestSoundSource.
---------------------------------------------------------------------*/

void SS_TestTimer
   (
   task *Task
   )

   {
   ( *( int * )( Task->data ) )++;
   }


/*---------------------------------------------------------------------
   Function: SS_TestSoundSource

   Detect if the Sound Source is located at the specified port.
---------------------------------------------------------------------*/

int SS_TestSoundSource
   (
   int port
   )

   {
   int   present;
   task *timer;
   volatile int ticks;
   int   i;

   present = FALSE;

   timer = TS_ScheduleTask( SS_TestTimer, 140, 1, &ticks );
   TS_Dispatch();

   outp( port + 2, 4 );

   ticks = 0;

   while( ticks < 4 )
      {
      // Do nothing for a while
      }

   TS_Terminate( timer );

   if ( ( inp( port + 1 ) & 0x40 ) == 0 )
      {
      for( i = 32; i > 0; i-- )
         {
         outp( port, 0x80 );
         outp( port + 2, SS_OffCommand );
         outp( port + 2, 4 );
         }

      if ( inp( port + 1 ) & 0x40 )
         {
         present = TRUE;
         }
      }

   outp( port + 2, SS_OffCommand );

   return( present );
   }


/*---------------------------------------------------------------------
   Function: SS_DetectSoundSource

   Detects which port the Sound Source is located.
---------------------------------------------------------------------*/

int SS_DetectSoundSource
   (
   void
   )

   {
   if ( USER_CheckParameter( SELECT_SOUNDSOURCE_PORT1 ) )
      {
      SS_Port = SS_Port1;
      return( TRUE );
      }

   if ( USER_CheckParameter( SELECT_SOUNDSOURCE_PORT2 ) )
      {
      SS_Port = SS_Port2;
      return( TRUE );
      }

   if ( USER_CheckParameter( SELECT_SOUNDSOURCE_PORT3 ) )
      {
      SS_Port = SS_Port3;
      return( TRUE );
      }

   if ( SS_TestSoundSource( SS_Port1 ) )
      {
      SS_Port = SS_Port1;
      return( TRUE );
      }

   if ( SS_TestSoundSource( SS_Port2 ) )
      {
      SS_Port = SS_Port2;
      return( TRUE );
      }

   if ( SS_TestSoundSource( SS_Port3 ) )
      {
      SS_Port = SS_Port3;
      return( TRUE );
      }

   return( FALSE );
   }


/*---------------------------------------------------------------------
   Function: SS_Init

   Initializes the Sound Source prepares the module to play digitized
   sounds.
---------------------------------------------------------------------*/

int SS_Init
   (
   int soundcard
   )

   {
   int status;

   if ( SS_Installed )
      {
      SS_Shutdown();
      }

   if ( ( soundcard == TandySoundSource ) ||
      ( USER_CheckParameter( SELECT_TANDY_SOUNDSOURCE ) ) )
      {
      // Tandy
      SS_OffCommand = 0x0e;
      }
   else
      {
      // Disney
      SS_OffCommand = 0x0c;
      }

   status = SS_DetectSoundSource();
   if ( !status )
      {
      SS_SetErrorCode( SS_NotFound );
      return( SS_Warning );
      }

   status = SS_LockMemory();
   if ( status != SS_Ok )
      {
      SS_UnlockMemory();
      return( status );
      }

   status = SS_Ok;

   outp( SS_Port + 2, 4 );

   SS_SoundPlaying = FALSE;

   SS_SetCallBack( NULL );

   SS_BufferStart = NULL;

   SS_Installed = TRUE;

   SS_SetErrorCode( status );
   return( status );
   }


/*---------------------------------------------------------------------
   Function: SS_Shutdown

   Ends transfer of sound data to the Sound Source.
---------------------------------------------------------------------*/

void SS_Shutdown
   (
   void
   )

   {
   // Halt the transfer
   SS_StopPlayback();

   outp( SS_Port + 2, SS_OffCommand );

   SS_SoundPlaying = FALSE;

   SS_BufferStart = NULL;

   SS_SetCallBack( NULL );

   SS_UnlockMemory();

   SS_Installed = FALSE;
   }


/*---------------------------------------------------------------------
   Function: SS_UnlockMemory

   Unlocks all neccessary data.
---------------------------------------------------------------------*/

void SS_UnlockMemory
   (
   void
   )

   {
   DPMI_UnlockMemoryRegion( SS_LockStart, SS_LockEnd );
   DPMI_Unlock( SS_Installed );
   DPMI_Unlock( SS_Port );
   DPMI_Unlock( SS_OffCommand );
   DPMI_Unlock( SS_BufferStart );
   DPMI_Unlock( SS_BufferEnd );
   DPMI_Unlock( SS_CurrentBuffer );
   DPMI_Unlock( SS_BufferNum );
   DPMI_Unlock( SS_NumBuffers );
   DPMI_Unlock( SS_TotalBufferSize );
   DPMI_Unlock( SS_TransferLength );
   DPMI_Unlock( SS_CurrentLength );
   DPMI_Unlock( SS_SoundPtr );
   DPMI_Unlock( SS_SoundPlaying );
   DPMI_Unlock( SS_Timer );
   DPMI_Unlock( SS_CallBack );
   DPMI_Unlock( SS_ErrorCode );
   }


/*---------------------------------------------------------------------
   Function: SS_LockMemory

   Locks all neccessary data.
---------------------------------------------------------------------*/

int SS_LockMemory
   (
   void
   )

   {
   int status;

   status  = DPMI_LockMemoryRegion( SS_LockStart, SS_LockEnd );
   status |= DPMI_Lock( SS_Installed );
   status |= DPMI_Lock( SS_Port );
   status |= DPMI_Lock( SS_OffCommand );
   status |= DPMI_Lock( SS_BufferStart );
   status |= DPMI_Lock( SS_BufferEnd );
   status |= DPMI_Lock( SS_CurrentBuffer );
   status |= DPMI_Lock( SS_BufferNum );
   status |= DPMI_Lock( SS_NumBuffers );
   status |= DPMI_Lock( SS_TotalBufferSize );
   status |= DPMI_Lock( SS_TransferLength );
   status |= DPMI_Lock( SS_CurrentLength );
   status |= DPMI_Lock( SS_SoundPtr );
   status |= DPMI_Lock( SS_SoundPlaying );
   status |= DPMI_Lock( SS_Timer );
   status |= DPMI_Lock( SS_CallBack );
   status |= DPMI_Lock( SS_ErrorCode );

   if ( status != DPMI_Ok )
      {
      SS_UnlockMemory();
      SS_SetErrorCode( SS_DPMI_Error );
      return( SS_Error );
      }

   return( SS_Ok );
   }
